﻿module Mueller.CoffeeMonitor.Notification

    open System
    open System.Drawing
    open System.Diagnostics
    open System.Reflection
    open System.Threading
    open System.Windows.Forms

    type Message =
        | Info of string
        | Warning of string
        | CompileSuccess of array<string>
        | CompileError of string * string
        | Exception of Exception
        | GetLast of AsyncReplyChannel<Message>
        | None

    let dispose (d: #IDisposable) = d.Dispose()
    let notify = new NotifyIcon(Text = "CoffeeScript Automatic Compiler",
                                Icon = new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream("CoffeeMonitor.ico")),
                                ContextMenu = new ContextMenu())

    let init (forceCompileAction: unit -> unit) (showForm: unit -> unit) =
        notify.ContextMenu.MenuItems.Clear()
        [|
            new MenuItem("Force Compile All",
                         fun _ _ -> forceCompileAction());
            new MenuItem("Options",
                         fun _ _ -> showForm());
            new MenuItem("-");
            new MenuItem("Exit", 
                         fun _ _ -> notify.Visible <- false
                                    notify.Dispose()
                                    Application.Exit())
        |] |> notify.ContextMenu.MenuItems.AddRange
        notify.Visible <- true

    let private agent =
        let context = SynchronizationContext.Current
        Debug.Assert(not <| Object.ReferenceEquals(context, null))
        MailboxProcessor.Start(fun inbox ->
            let rec loop lastMsg = async {
                let! msg = inbox.Receive()
                do! Async.SwitchToContext context
                match msg with
                | Info text ->
                    notify.ShowBalloonTip(500, "CoffeeScript", text, ToolTipIcon.Info)
                    do! Async.Sleep 500
                | Warning text ->
                    notify.ShowBalloonTip(750, "CoffeeScript", text, ToolTipIcon.Warning)
                    do! Async.Sleep 750
                | CompileSuccess files ->
                    let items = String.Join(Environment.NewLine, files)
                    notify.ShowBalloonTip(750, "CoffeeScript Compiled", items, ToolTipIcon.Info)
                    do! Async.Sleep 750
                | CompileError(fileName, text) ->
                    notify.ShowBalloonTip(5000, fileName, text, ToolTipIcon.Error)
                    do! Async.Sleep 5000
                | Exception ex ->
                    notify.ShowBalloonTip(5000, "Error", ex.Message, ToolTipIcon.Error)
                    do! Async.Sleep 5000
                | GetLast channel ->
                    channel.Reply lastMsg
                | None -> ()
                
                return! loop msg
            }
            loop None
        )

    let displayLastMessage _ =
        let msg = agent.PostAndReply(fun reply -> GetLast reply)
        let title, text =
            match msg with
            | Info s    -> "Information", s
            | Warning s -> "Warning", s
            | CompileSuccess files ->
                "Compile Success", sprintf "Successfully Compiled:\r\n%s" (String.Join(Environment.NewLine, files))
            | CompileError(fileName, text) ->
                "Compile Error", sprintf "%s:\r\n%s" fileName text
            | Exception ex ->
                "Exception", ex.ToString()
            | _ -> "", ""
        if title <> "" then
            let form = new Form(Text = title, Width = 300, Height = 300, TopMost = true, FormBorderStyle = FormBorderStyle.FixedDialog,
                                Icon = new Icon(Assembly.GetExecutingAssembly().GetManifestResourceStream("CoffeeMonitor.ico")))
            form.Controls.Add(new TextBox(Multiline = true, ScrollBars = ScrollBars.Vertical, Dock = DockStyle.Fill, 
                                          Lines = text.Split([|'\n';'\r'|], StringSplitOptions.RemoveEmptyEntries)))
            form.Show() |> ignore

    notify.BalloonTipClicked
    |> Observable.add displayLastMessage

    let info fmt =
        Printf.ksprintf (fun s -> agent.Post(Info s)) fmt

    let warn fmt =
        Printf.ksprintf (fun s -> agent.Post(Warning s)) fmt

    let compileError fileName msg =
        agent.Post(CompileError(fileName, msg))

    let compileSuccess files =
        if not (Array.isEmpty files) then
            agent.Post(CompileSuccess files)

    let error ex =
        agent.Post(Exception ex)

